TelecomManager.addNewIncomingCall завершается с ошибкой, когда звонок уже звонитAndroid

Форум для тех, кто программирует под Android
Ответить
Anonymous
 TelecomManager.addNewIncomingCall завершается с ошибкой, когда звонок уже звонит

Сообщение Anonymous »

Я разрабатываю приложение для вызовов VoIP на Android со сторонним стеком SIP.
По большей части это приложение работает нормально, но при входящем (и оставшемся без ответа) вызове происходит сбой addNewIncomingCall и ConnectionService.onCreateIncomingConnectionFailed называется.
Есть идеи, чего мне не хватает?
Демо-код:
в AndroidManifest.xml







...







MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ActivityCompat.requestPermissions(this, arrayOf(
Manifest.permission.CALL_PHONE,
Manifest.permission.READ_PHONE_STATE,
Manifest.permission.READ_PHONE_NUMBERS,
), 0)

enableEdgeToEdge()
setContent {
MultipleIncomingCallTestTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}

@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
val context = LocalContext.current
val helper = remember { MyTelecomHelper(context, context.getSystemService(Context.TELECOM_SERVICE) as TelecomManager) }
Column(
horizontalAlignment = Alignment.CenterHorizontally,modifier = modifier
){
Button(
onClick = {
val account = if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.READ_PHONE_STATE
) != PackageManager.PERMISSION_GRANTED
) {
return@Button
}else{
helper.initPhoneAccount()
}
helper.startIncoming("1234", "User A", account.accountHandle)
},
modifier = modifier
){
Text("Incoming Call A")
}
Button(
onClick = {
val account = if (ActivityCompat.checkSelfPermission(
context,
Manifest.permission.READ_PHONE_STATE
) != PackageManager.PERMISSION_GRANTED
) {
return@Button
}else{
helper.initPhoneAccount()
}
helper.startIncoming("5678", "User B", account.accountHandle)
},
modifier = modifier
){
Text("Incoming Call B")
}
Button(
onClick = {
helper.disconnect("1234")
},
modifier = modifier
){
Text("Disconnect Call A")
}
Button(
onClick = {
helper.disconnect("5678")
},
modifier = modifier
){
Text("Disconnect Call B")
}
}
}

MyTelecomHelper.kt
class MyTelecomHelper(
private val context: Context,
private val telecomManager: TelecomManager
) {

private val connections = mutableListOf()
init{
MyConnectionService.addConnectionStateChangedListener( object:ConnectionStateChangedListener{
override fun onStateChanged(state: Int, connection: MyConnection) {
when(state){
Connection.STATE_RINGING->{
connections.add(connection)
}
Connection.STATE_DISCONNECTED->{
connections.remove(connection)
}
}
}
})
}

private fun createAccount(context: Context): PhoneAccount {
val accountHandle = PhoneAccountHandle(
ComponentName(context, MyConnectionService::class.java),
context.packageName
)
val account = PhoneAccount.builder(accountHandle, "Test Account")
.setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
.setSupportedUriSchemes(listOf(PhoneAccount.SCHEME_TEL))
.build()
try {
telecomManager.registerPhoneAccount(account)
} catch (e: Exception) {
Log.e(this.javaClass.simpleName, e.toString())
}
return account
}
@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
private fun findExistingAccount(context: Context): PhoneAccount? {
val connectionService = ComponentName(
context,
MyConnectionService::class.java
)

val targetPhoneAccountHandle =
telecomManager.selfManagedPhoneAccounts.firstOrNull { phoneAccountHandle ->
phoneAccountHandle.componentName == connectionService
}
return telecomManager.getPhoneAccount(targetPhoneAccountHandle)
}

@RequiresPermission(Manifest.permission.READ_PHONE_STATE)
fun initPhoneAccount(): PhoneAccount {
return findExistingAccount(context) ?: return createAccount(context)
}

fun startIncoming(number: String, name: String, accountHandle: PhoneAccountHandle) {
telecomManager.addNewIncomingCall(
accountHandle,
Bundle().apply {
putParcelable(
TelecomManager.EXTRA_INCOMING_CALL_EXTRAS,
Bundle().apply {
putString("name", name)
putString("number", number)
}
)
}
)
}

fun disconnect(number: String) {
connections.lastOrNull {
(it.state == Connection.STATE_DIALING
|| it.state == Connection.STATE_RINGING
|| it.state == Connection.STATE_ACTIVE
|| it.state == Connection.STATE_HOLDING)
&& it.address.schemeSpecificPart == number
}?.let {
it.setDisconnected(DisconnectCause(DisconnectCause.LOCAL))
connections.remove(it)
}
}
}

MyConnectionService.kt
class MyConnectionService : ConnectionService() {
override fun onCreateIncomingConnection(
connectionManagerPhoneAccount: PhoneAccountHandle?,
request: ConnectionRequest?
): Connection {
val bundle = request?.extras?.getBundle(TelecomManager.EXTRA_INCOMING_CALL_EXTRAS)
val name = bundle?.getString("name")
val number = bundle?.getString("number")
val connection = MyConnection(stateChangedListeners).apply {
setAddress(Uri.fromParts(PhoneAccount.SCHEME_TEL,number,null), TelecomManager.PRESENTATION_ALLOWED)
setCallerDisplayName(name, TelecomManager.PRESENTATION_ALLOWED)
setRinging()
}
Log.d(MyConnectionService::class.java.simpleName, "Incoming Connection created.\n" +
"name = $name\n" +
"number = $number")
return connection
}

override fun onCreateIncomingConnectionFailed(
connectionManagerPhoneAccount: PhoneAccountHandle?,
request: ConnectionRequest?
) {

Log.d(MyConnectionService::class.java.simpleName, "Incoming Connection failed.\n" +
"connectionManagerPhoneAccount = $connectionManagerPhoneAccount\n" +
"request = $request")
}
companion object{
private val stateChangedListeners = mutableListOf()

fun addConnectionStateChangedListener(listener: ConnectionStateChangedListener) {
stateChangedListeners.add(listener)
}

}
}

MyConnection.kt
interface ConnectionStateChangedListener {
fun onStateChanged(state: Int, connection: MyConnection)
}

class MyConnection(
private val stateChangeListeners: MutableList = mutableListOf()
) : Connection() {
init{
audioModeIsVoip = true
connectionProperties = PROPERTY_SELF_MANAGED
connectionCapabilities = CAPABILITY_MUTE or CAPABILITY_HOLD or CAPABILITY_SUPPORT_HOLD
setInitializing()
}

override fun onStateChanged(state: Int) {
Log.d(MyConnection::class.java.simpleName, "onStateChanged: $address $state")
stateChangeListeners.forEach { listener ->
listener.onStateChanged(state, this)
}
}

override fun onAnswer() {
super.onAnswer()
setActive()
}

override fun onReject() {
super.onReject()
setDisconnected(DisconnectCause(DisconnectCause.REJECTED))
}

override fun onDisconnect() {
super.onDisconnect()
setDisconnected(DisconnectCause(DisconnectCause.LOCAL))
}

override fun onHold() {
super.onHold()
setOnHold()
}

override fun onUnhold() {
super.onUnhold()
setActive()
}

override fun onMuteStateChanged(isMuted: Boolean) {
super.onMuteStateChanged(isMuted)
}
}


Подробнее здесь: https://stackoverflow.com/questions/792 ... dy-ringing
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»