电信框架概览

Android 电信框架(也简称为“Telecom”)管理 Android 设备上的音频和视频通话。这包括基于 SIM 卡的通话(例如使用电话框架的通话)以及实现 Core-Telecom Jetpack 库的 VoIP 通话。

Telecom 管理的主要组件是 ConnectionServiceInCallService

ConnectionService 的实现基于 PSTN 等技术,用于将通话连接到其他方。手机上最常见的 ConnectionService 实现是电话 ConnectionService。它用于连接运营商通话。

InCallService 的实现为 Telecom 管理的通话提供用户界面,并允许用户控制和交互这些通话。最常见的 InCallService 实现是设备捆绑的电话应用。

Telecom 充当交换机。它将 ConnectionService 实现提供的通话路由到 InCallService 实现提供的通话用户界面。

您可能出于以下原因需要实现 Telecom API

创建替代电话应用

要在 Android 设备上创建默认电话应用的替代品,请实现 InCallService API。您的实现必须满足以下要求

  • 它不得具有任何通话功能,并且必须仅包含通话用户界面。
  • 它必须处理 Telecom 框架知晓的所有通话,并且不得对通话性质做出假设。例如,它不得假定通话是基于 SIM 卡的电话通话,也不得实施基于任何特定 ConnectionService 的通话限制,例如对视频通话实施电话限制。

有关详细信息,请参阅 InCallService

集成通话解决方案

要将您的通话解决方案集成到 Android 中,您有以下选项

  • 实现自管理的 Core-Telecom Jetpack 库:此选项非常适合不希望在默认电话应用中显示其通话,也不希望在其用户界面中显示其他通话的独立通话应用开发者。

    当您与 Core-Telecom Jetpack 库集成时,您有助于您的应用不仅与设备上的系统电话通话互操作,而且与集成到 Telecom 的其他独立通话应用互操作。Core-Telecom 库还管理音频路由和焦点。有关详细信息,请参阅构建通话应用

  • 实现托管的 ConnectionService API:此选项有助于开发依赖现有设备电话应用提供通话用户界面的通话解决方案。示例包括第三方 SIP 通话和 VoIP 通话服务的实现。有关详细信息,请参阅 getDefaultDialerPackage()

    单独的 ConnectionService 仅提供连接通话的手段。它没有关联的用户界面。

  • 同时实现 InCallService 和 ConnectionService API:如果您想创建自己的基于 ConnectionService 的通话解决方案,并拥有自己的用户界面,同时还在同一用户界面中显示所有其他 Android 通话,那么此选项是理想之选。当您使用此方法时,您的 InCallService 实现不得对所显示通话的来源做任何假设。此外,您的 ConnectionService 实现必须在未将默认电话应用设置为您的自定义 InCallService 的情况下继续工作。

甄别来电

运行 Android 10(API 级别 29)或更高版本的设备允许您的应用将用户通讯录中不存在的号码来电识别为潜在的垃圾电话。用户可以选择静默拒绝垃圾电话。为了在用户未接电话时提供更高的透明度,这些被阻止电话的信息会记录在通话记录中。使用 Android 10 API 消除了为了提供来电甄别和来电显示功能而需要从用户那里获取 READ_CALL_LOG 权限的要求。

您可以使用 CallScreeningService 实现来甄别来电。当号码不在用户联系人列表中时,对任何新的来电或去电调用 onScreenCall() 函数。您可以检查 Call.Details 对象以获取有关通话的信息。具体来说,getCallerNumberVerificationStatus() 函数包含网络提供商关于对方号码的信息。如果验证状态失败,则表明该通话来自无效号码或可能是垃圾电话。

Kotlin

class ScreeningService : CallScreeningService() {
    // This function is called when an ingoing or outgoing call
    // is from a number not in the user's contacts list
    override fun onScreenCall(callDetails: Call.Details) {
        // Can check the direction of the call
        val isIncoming = callDetails.callDirection == Call.Details.DIRECTION_INCOMING

        if (isIncoming) {
            // the handle (e.g. phone number) that the Call is currently connected to
            val handle: Uri = callDetails.handle

            // determine if you want to allow or reject the call
            when (callDetails.callerNumberVerificationStatus) {
                Connection.VERIFICATION_STATUS_FAILED -> {
                    // Network verification failed, likely an invalid/spam call.
                }
                Connection.VERIFICATION_STATUS_PASSED -> {
                    // Network verification passed, likely a valid call.
                }
                else -> {
                    // Network could not perform verification.
                    // This branch matches Connection.VERIFICATION_STATUS_NOT_VERIFIED.
                }
            }
        }
    }
}

Java

class ScreeningService extends CallScreeningService {
    @Override
    public void onScreenCall(@NonNull Call.Details callDetails) {
        boolean isIncoming = callDetails.getCallDirection() == Call.Details.DIRECTION_INCOMING;

        if (isIncoming) {
            Uri handle = callDetails.getHandle();

            switch (callDetails.getCallerNumberVerificationStatus()) {
                case Connection.VERIFICATION_STATUS_FAILED:
                    // Network verification failed, likely an invalid/spam call.
                    break;
                case Connection.VERIFICATION_STATUS_PASSED:
                    // Network verification passed, likely a valid call.
                    break;
                default:
                    // Network could not perform verification.
                    // This branch matches Connection.VERIFICATION_STATUS_NOT_VERIFIED
            }
        }
    }
}

onScreenCall() 函数设置为调用 respondToCall() 以告知系统如何响应新通话。此函数接受一个 CallResponse 参数,您可以使用该参数告诉系统阻止该通话,或像用户自己拒绝一样拒绝它,或使其静音。您还可以告诉系统完全跳过将此通话添加到设备的通话记录中。

Kotlin

// Tell the system how to respond to the incoming call
// and if it should notify the user of the call.
val response = CallResponse.Builder()
    // Sets whether the incoming call should be blocked.
    .setDisallowCall(false)
    // Sets whether the incoming call should be rejected as if the user did so manually.
    .setRejectCall(false)
    // Sets whether ringing should be silenced for the incoming call.
    .setSilenceCall(false)
    // Sets whether the incoming call should not be displayed in the call log.
    .setSkipCallLog(false)
    // Sets whether a missed call notification should not be shown for the incoming call.
    .setSkipNotification(false)
    .build()

// Call this function to provide your screening response.
respondToCall(callDetails, response)

Java

// Tell the system how to respond to the incoming call
// and if it should notify the user of the call.
CallResponse.Builder response = new CallResponse.Builder();
// Sets whether the incoming call should be blocked.
response.setDisallowCall(false);
// Sets whether the incoming call should be rejected as if the user did so manually.
response.setRejectCall(false);
// Sets whether ringing should be silenced for the incoming call.
response.setSilenceCall(false);
// Sets whether the incoming call should not be displayed in the call log.
response.setSkipCallLog(false);
// Sets whether a missed call notification should not be shown for the incoming call.
response.setSkipNotification(false);

// Call this function to provide your screening response.
respondToCall(callDetails, response.build());

您必须在 manifest 文件中注册 CallScreeningService 实现,并带有适当的 intent 过滤器和权限,以便系统能够正确触发它。

<service
    android:name=".ScreeningService"
    android:permission="android.permission.BIND_SCREENING_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.CallScreeningService" />
    </intent-filter>
</service>

重定向通话

运行 Android 10 或更高版本的设备管理通话 Intent 的方式与运行 Android 9 或更低版本的设备不同。在 Android 10 及更高版本上,ACTION_NEW_OUTGOING_CALL 广播已弃用,并由 CallRedirectionService API 取代。CallRedirectionService 提供接口供您修改 Android 平台发出的去电。例如,第三方应用可能会取消通话并通过 VoIP 进行重定向。

Kotlin

class RedirectionService : CallRedirectionService() {
    override fun onPlaceCall(
        handle: Uri,
        initialPhoneAccount: PhoneAccountHandle,
        allowInteractiveResponse: Boolean
    ) {
        // Determine if the call should proceed, be redirected, or cancelled.
        val callShouldProceed = true
        val callShouldRedirect = false
        when {
            callShouldProceed -> {
                placeCallUnmodified()
            }
            callShouldRedirect -> {
                // Update the URI to point to a different phone number or modify the
                // PhoneAccountHandle and redirect.
                redirectCall(handle, initialPhoneAccount, true)
            }
            else -> {
                cancelCall()
            }
        }
    }
}

Java

class RedirectionService extends CallRedirectionService {
    @Override
    public void onPlaceCall(
            @NonNull Uri handle,
            @NonNull PhoneAccountHandle initialPhoneAccount,
            boolean allowInteractiveResponse
    ) {
        // Determine if the call should proceed, be redirected, or cancelled.
        // Your app should implement this logic to determine the redirection.
        boolean callShouldProceed = true;
        boolean callShouldRedirect = false;
        if (callShouldProceed) {
            placeCallUnmodified();
        } else if (callShouldRedirect) {
            // Update the URI to point to a different phone number or modify the
            // PhoneAccountHandle and redirect.
            redirectCall(handle, initialPhoneAccount, true);
        } else {
            cancelCall();
        }
    }
}

您必须在 manifest 中注册此服务,以便系统能够正确启动它。

<service
    android:name=".RedirectionService"
    android:permission="android.permission.BIND_CALL_REDIRECTION_SERVICE">
    <intent-filter>
        <action android:name="android.telecom.CallRedirectionService"/>
    </intent-filter>
</service>

要使用重定向服务,您的应用必须从 RoleManager 请求通话重定向角色。这将询问用户是否允许您的应用处理通话重定向。如果您的应用未被授予此角色,则您的重定向服务将不会被使用。

您应该在用户启动您的应用时检查您的应用是否拥有此角色,以便您可以根据需要请求它。您会启动由 RoleManager 创建的 Intent,因此请确保覆盖 onActivityResult() 函数以处理用户的选择。

Kotlin

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // Tell the system that you want your app to handle call redirects. This
        // is done by using the RoleManager to register your app to handle redirects.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            val roleManager = getSystemService(Context.ROLE_SERVICE) as RoleManager
            // Check if the app needs to register call redirection role.
            val shouldRequestRole = roleManager.isRoleAvailable(RoleManager.ROLE_CALL_REDIRECTION) &&
                    !roleManager.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION)
            if (shouldRequestRole) {
                val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION)
                startActivityForResult(intent, REDIRECT_ROLE_REQUEST_CODE)
            }
        }
    }

    companion object {
        private const val REDIRECT_ROLE_REQUEST_CODE = 1
    }
}

Java

class MainActivity extends AppCompatActivity {
    private static final int REDIRECT_ROLE_REQUEST_CODE = 0;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Tell the system that you want your app to handle call redirects. This
        // is done by using the RoleManager to register your app to handle redirects.
        if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) {
            RoleManager roleManager = (RoleManager) getSystemService(Context.ROLE_SERVICE);
            // Check if the app needs to register call redirection role.
            boolean shouldRequestRole = roleManager.isRoleAvailable(RoleManager.ROLE_CALL_REDIRECTION) &&
                    !roleManager.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION);
            if (shouldRequestRole) {
                Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION);
                startActivityForResult(intent, REDIRECT_ROLE_REQUEST_CODE);
            }
        }
    }
}