电信框架概述

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

Telecom 管理的主要组件是 ConnectionServiceInCallService

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

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

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

您可能希望出于以下原因实现 Telecom API

创建替换电话应用

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

  • 它不能有任何通话功能,并且必须仅由通话的用户界面组成。
  • 它必须处理 Telecom 框架感知的所有通话,并且不要对通话的性质做出假设。例如,它不应假设通话是基于 SIM 卡的电话通话,也不应实施基于任何一个 ConnectionService 的通话限制,例如对视频通话实施电话限制。

有关更多信息,请参阅 InCallService

集成呼叫解决方案

要将您的呼叫解决方案集成到 Android 中,您可以选择以下选项

  • **实现自管理 ConnectionService API:**此选项非常适合独立呼叫应用的开发者,他们不希望在默认电话应用中显示其通话,也不希望在他们的用户界面中显示其他通话。

    当您使用自管理 ConnectionService 时,您可以帮助您的应用不仅与设备上的原生电话通话交互,还与实现此 API 的其他独立呼叫应用交互。自管理 ConnectionService API 还管理音频路由和焦点。有关详细信息,请参阅 构建呼叫应用

  • 实现托管的 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());

您必须在清单文件中使用适当的意图过滤器和权限注册 CallScreeningService 实现,以便系统能够正确触发它。

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

重定向呼叫

运行 Android 10 或更高版本的设备与运行 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();
        }
    }
}

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

<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 创建的意图,因此请确保覆盖 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);
            }
        }
    }
}