返回

Android Kotlin 后台拨打电话失败? 解决方案详解!

Android

Android Kotlin:使用 Connection Service 和 Phone Account 实现后台拨打电话却无法接通的解决方案

你是否正在为你的 Android 应用开发一个需要在后台悄悄拨打电话的功能而头疼?你是否已经尝试过使用 Connection ServicePhone Account,却发现电话根本无法接通?放心,你不是一个人!本文将深入剖析这个问题背后的症结,并提供一套完整的解决方案,助你的应用顺利实现后台拨打电话的功能。

迷雾重重:后台拨打电话为何失败?

许多开发者在使用 Connection ServicePhone Account 构建后台拨打电话功能时,都会遇到一个共同的难题:明明已经成功建立连接,拨号界面也正常显示,但对方的电话却始终没有反应。这背后的“元凶”往往是以下几个问题:

  • 权限壁垒: 即使你的应用已经获得了 CALL_PHONE 权限,但在 Android 10 及以上版本中,你还需要额外申请 READ_PHONE_STATE 权限才能获取通话状态,否则系统会“无情”地拒绝你的拨号请求。
  • PhoneAccount 类型设置失误: PhoneAccountCAPABILITY 类型需要根据你的实际需求进行精准设置。如果你只想建立连接而不需要管理通话状态,那么 CAPABILITY_CONNECTION_MANAGER 是你的最佳选择。但如果你需要全面掌控通话过程,则必须使用 CAPABILITY_SELF_MANAGED 并实现相应的回调方法,否则系统将无法理解你的“意图”。
  • Connection 状态处理不当: 当你选择使用 CAPABILITY_SELF_MANAGED 类型时,你需要在 Connection 类的回调方法中妥善处理各种通话状态,例如通过 onStateChanged() 方法监听连接状态变化,通过 onAnswer() 方法接听电话等。如果这些状态处理不当,你的拨打电话操作就会像“断线的风筝”一样失去控制。

###拨开迷雾:解决方案详解

为了攻克上述难题,我们可以采取以下三个步骤:

  1. 扫清权限障碍

    首先,你需要确保你的应用在 AndroidManifest.xml 文件中声明了以下两个至关重要的权限:

    <uses-permission android:name="android.permission.CALL_PHONE" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    

    然后,你需要在代码中动态申请这两个权限:

    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED ||
        ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE, Manifest.permission.READ_PHONE_STATE), REQUEST_CODE)
    } else {
        // 权限已获取,可以开始拨打电话了!
    }
    
  2. 精准设置 PhoneAccount 类型

    你需要根据你的实际需求选择合适的 CAPABILITY 类型。如果只是想建立连接,使用 CAPABILITY_CONNECTION_MANAGER 即可:

    val phoneAccount = PhoneAccount.Builder(phoneAccountHandle, "YourAppLabel")
        .setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER)
        .build()
    

    如果需要全面控制通话过程,则需使用 CAPABILITY_SELF_MANAGED 并实现相应的回调方法:

    val phoneAccount = PhoneAccount.Builder(phoneAccountHandle, "YourAppLabel")
        .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
        .build()
    
  3. 妥善处理 Connection 状态变化

    你需要在 Connection 类的回调方法中处理好各种通话状态,例如:

    class YourConnection(
        phoneAccountHandle: PhoneAccountHandle?,
        address: Uri?
    ) : Connection() {
    
        override fun onStateChanged(state: Int) {
            when (state) {
                STATE_RINGING -> {
                    // 电话正在响铃
                }
                STATE_ACTIVE -> {
                    // 通话已接通
                }
                STATE_DISCONNECTED -> {
                    // 通话已断开
                }
                // 处理其他状态
            }
        }
    
        override fun onAnswer() {
            // 接听电话
        }
    
        // 实现其他回调方法
    }
    

实践出真知:代码示例

为了让你更加清晰地理解上述解决方案,下面提供一个修改后的代码示例,可以实现后台拨打电话并让对方电话响铃:

// MainActivity.kt
fun makeTelecomCall() {
    if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED ||
        ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED) {
        ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE, Manifest.permission.READ_PHONE_STATE), REQUEST_CODE)
    } else {
        val phoneAccountHandle = PhoneAccountHandle(ComponentName(this, MyConnectionService::class.java), "UniqueIdentifier")
        val phoneAccount = PhoneAccount.Builder(phoneAccountHandle, "YourAppLabel")
            .setCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)
            .build()
        val telecomManager = getSystemService(Context.TELECOM_SERVICE) as TelecomManager
        telecomManager.registerPhoneAccount(phoneAccount)

        val uri = Uri.fromParts(PhoneAccount.SCHEME_TEL, "xxxxxxxxxx", null)
        val bundle = Bundle().apply {
            putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle)
        }
        telecomManager.placeCall(uri, bundle)
    }
}

// MyConnectionService.kt
class MyConnectionService : ConnectionService() {

    override fun onCreateOutgoingConnection(phoneAccountHandle: PhoneAccountHandle?, connectionRequest: ConnectionRequest?): Connection? {
        return YourConnection(phoneAccountHandle, connectionRequest?.address)
    }

    // ... 其他回调方法
}

class YourConnection(phoneAccountHandle: PhoneAccountHandle?, address: Uri?) : Connection() {
    init {
        setAddress(address, TelecomManager.PRESENTATION_ALLOWED)
        setConnectionCapabilities(CAPABILITY_HOLD | CAPABILITY_MUTE)
        setAudioModeIsVoip(true)
    }

    override fun onStateChanged(state: Int) {
        Log.i("CONNECTION", state.toString())
    }

    // ... 其他回调方法
}

常见问题解答

为了帮助你更好地理解和应用上述解决方案,这里列举了五个常见问题及其解答:

1. 为什么我的应用在 Android 10 以下版本可以正常拨打电话,但在 Android 10 及以上版本却无法接通?

这是因为 Android 10 及以上版本加强了对后台应用的权限管理,即使你的应用已经获得了 CALL_PHONE 权限,也需要额外申请 READ_PHONE_STATE 权限才能获取通话状态,否则系统将阻止你的拨号请求。

2. CAPABILITY_CONNECTION_MANAGERCAPABILITY_SELF_MANAGED 有什么区别?我应该如何选择?

CAPABILITY_CONNECTION_MANAGER 适用于只需要建立连接而不需要管理通话状态的场景,例如一些只需要创建连接进行数据传输的应用。CAPABILITY_SELF_MANAGED 适用于需要全面控制通话过程的场景,例如需要监听通话状态、接听电话、挂断电话等的应用。

3. 为什么我使用了 CAPABILITY_SELF_MANAGED 类型,但仍然无法接听电话?

这可能是因为你没有在 Connection 类的 onAnswer() 回调方法中实现接听电话的逻辑。你需要在该方法中调用 setActive() 方法将连接状态设置为活动状态,才能成功接听电话。

4. 如何监听通话状态的变化?

你可以通过 Connection 类的 onStateChanged() 回调方法监听通话状态的变化。该方法会返回一个整型参数,表示当前的通话状态,例如 STATE_RINGING 表示电话正在响铃,STATE_ACTIVE 表示通话已接通,STATE_DISCONNECTED 表示通话已断开等。

5. 如何实现静默拨打电话,即不显示拨号界面?

你需要在创建 Connection 对象时,将 setRingbackRequested(false) 方法设置为 false,并将 setAudioModeIsVoip(true) 方法设置为 true,这样就可以实现静默拨打电话,不会显示拨号界面,也不会播放铃声。

总结

通过以上步骤,相信你已经能够解决 Android Kotlin 应用中使用 Connection ServicePhone Account 实现后台拨打电话却无法接通的问题。

SEO关键词: Android, Kotlin, Connection Service, Phone Account, 后台拨打电话, 电话无法接通, 解决方案, CAPABILITY_CONNECTION_MANAGER, CAPABILITY_SELF_MANAGED, READ_PHONE_STATE

SEO: 本文提供了一种使用 Android Kotlin 中的 Connection ServicePhone Account 实现后台拨打电话的解决方案,并详细介绍了如何解决电话无法接通的常见问题,帮助开发者轻松实现应用的后台拨号功能。