Я разрабатываю приложение для вызовов 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
TelecomManager.addNewIncomingCall завершается с ошибкой, когда звонок уже звонит ⇐ Android
Форум для тех, кто программирует под Android
1734337337
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)
}
}
Подробнее здесь: [url]https://stackoverflow.com/questions/79284017/telecommanager-addnewincomingcall-fails-when-a-call-is-already-ringing[/url]
Ответить
1 сообщение
• Страница 1 из 1
Перейти
- Кемерово-IT
- ↳ Javascript
- ↳ C#
- ↳ JAVA
- ↳ Elasticsearch aggregation
- ↳ Python
- ↳ Php
- ↳ Android
- ↳ Html
- ↳ Jquery
- ↳ C++
- ↳ IOS
- ↳ CSS
- ↳ Excel
- ↳ Linux
- ↳ Apache
- ↳ MySql
- Детский мир
- Для души
- ↳ Музыкальные инструменты даром
- ↳ Печатная продукция даром
- Внешняя красота и здоровье
- ↳ Одежда и обувь для взрослых даром
- ↳ Товары для здоровья
- ↳ Физкультура и спорт
- Техника - даром!
- ↳ Автомобилистам
- ↳ Компьютерная техника
- ↳ Плиты: газовые и электрические
- ↳ Холодильники
- ↳ Стиральные машины
- ↳ Телевизоры
- ↳ Телефоны, смартфоны, плашеты
- ↳ Швейные машинки
- ↳ Прочая электроника и техника
- ↳ Фототехника
- Ремонт и интерьер
- ↳ Стройматериалы, инструмент
- ↳ Мебель и предметы интерьера даром
- ↳ Cантехника
- Другие темы
- ↳ Разное даром
- ↳ Давай меняться!
- ↳ Отдам\возьму за копеечку
- ↳ Работа и подработка в Кемерове
- ↳ Давай с тобой поговорим...
Мобильная версия